﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="With_ScopeProperty.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the With_ScopeProperty type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using System.Windows;
    using Microsoft.Silverlight.Testing;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Testing;

    [TestClass, Tag("ScopeProperty")]
    public class With_ScopeProperty : SilverlightTest
    {
        #region Fields

        public static readonly object TestUnsetValue = new object();

        public static readonly ScopeProperty TestValueScopeProperty =
            ScopeProperty.RegisterAttached("ScopeTestValue", typeof (object), typeof (With_ScopeProperty),
                new ScopePropertyMetadata(TestUnsetValue, OnScopeTestValueChanged));

        public static readonly DependencyProperty TestValueProperty = TestValueScopeProperty.Property;

        #endregion

        public static void SetTestValue(DependencyObject obj, object value)
        {
            obj.SetValue(TestValueProperty, value);
        }

        public static int TestPropertyChangeCount { get; set; }

        private static void OnScopeTestValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TestPropertyChangeCount++;
        }

        public ScopePropertyTestView SystemUnderTestView;
        
        public static object GetTestValue(DependencyObject obj)
        {
            return obj.GetValue(TestValueProperty);
        }
        
        [TestInitialize]
        public virtual void Given()
        {
            SystemUnderTestView = new ScopePropertyTestView();
            ScopePropertyFallback.Instance.ClearValue(TestValueProperty);
            TestPanel.ClearValue(TestValueProperty);
            TestPropertyChangeCount = 0;
            SystemUnderTestView.ObserverLog.Clear();
        }

        public void AssertObserverLog(params object[] values)
        {
            ListAssert.AreEqual(values, SystemUnderTestView.ObserverLog);
        }

        public void AssertXamlObserverLog(params object[] values)
        {
            ListAssert.AreEqual(values, SystemUnderTestView.XamlObserverLog);
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_set : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Observed.SetValue(TestValueProperty, "Value");
            }

            [TestMethod]
            public void Should_forward_property_change_notification()
            {
                Assert.AreEqual(1, TestPropertyChangeCount);
            }

            [TestMethod]
            public void Should_initialize_scope()
            {
                Assert.IsNotNull(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Observed, TestValueProperty));    
            }
        }
        
        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_cleared : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Observed.SetValue(TestValueProperty, "Value");
                SystemUnderTestView.Observed.ClearValue(TestValueProperty);
            }

            [TestMethod]
            public void Should_clean_scope()
            {
                Assert.IsNull(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Observed, TestValueProperty));
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_set_on_observered : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Observed.SetValue(TestValueProperty, "Value");
            }

            [TestMethod]
            public void Should_update_observer()
            {
                AssertObserverLog("Value");
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_set_on_level1_ancestor_of_observer : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
            }

            [TestMethod]
            public void Should_update_observer_to_level1()
            {
                AssertObserverLog("Level1");
            }

            [TestMethod]
            public void Should_attach_behavior_to_level1_instance()
            {
                Assert.IsNotNull(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level1Ancestor, TestValueProperty));
            }

            [TestMethod]
            public void Should_subscribe_observer_to_behavior_on_level1_instance()
            {
                Assert.IsTrue(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level1Ancestor, TestValueProperty)
                    .HasObserver(SystemUnderTestView.Observer));
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_set_on_level2_ancestor_of_observered_after_level_1_ancestor_has_been_set : With_ScopeProperty
        {
            private ScopePropertyBehavior _oldLevel1ScopePropertyBehavior;

            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
                _oldLevel1ScopePropertyBehavior = ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level1Ancestor, TestValueProperty);
                SystemUnderTestView.Level2Ancestor.SetValue(TestValueProperty, "Level2");
            }

            [TestMethod]
            public void Should_update_to_level2()
            {
                AssertObserverLog("Level1", "Level2");
            }

            [TestMethod]
            public void Should_detach_behavior_from_level1_instance()
            {
                // TODO: Can we get rid of the behavior?
                // Assert.IsNull(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level1Ancestor, TestValueProperty));
            }

            [TestMethod]
            public void Should_unsubscribe_observer_from_behavior_that_was_on_level1_instance()
            {
                Assert.IsFalse(_oldLevel1ScopePropertyBehavior.HasObserver(SystemUnderTestView.Observer));
            }

            [TestMethod]
            public void Should_attach_behavior_to_level2_instance()
            {
                Assert.IsNotNull(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level2Ancestor, TestValueProperty));
            }

            [TestMethod]
            public void Should_subscribe_observer_to_behavior_on_level2_instance()
            {
                Assert.IsTrue(ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level2Ancestor, TestValueProperty)
                    .HasObserver(SystemUnderTestView.Observer));
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_cleared_on_level2_ancestor_of_observered_when_level1_ancestor_has_been_set : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
                SystemUnderTestView.Level2Ancestor.SetValue(TestValueProperty, "Level2");
                SystemUnderTestView.Level2Ancestor.ClearValue(TestValueProperty);
            }

            [TestMethod]
            public void Should_update_to_level1()
            {
                AssertObserverLog("Level1", "Level2", "Level1");
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_cleared_in_ancestory_of_observered : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level2Ancestor.SetValue(TestValueProperty, "Level1");
                ScopePropertyFallback.Instance.SetValue(TestValueProperty, "FallbackValue");
                SystemUnderTestView.Level2Ancestor.ClearValue(TestValueProperty);
            }

            [TestMethod]
            public void Should_update_with_fallback_value()
            {
                AssertObserverLog("Level1", "FallbackValue");
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_not_set : With_ScopeProperty
        {
            [TestMethod]
            public void Should_return_unset_value()
            {
                Assert.AreEqual(TestUnsetValue, SystemUnderTestView.Observer.Value);   
            }
        }

        /// <summary>
        /// When Loading Xaml Should Update On Load
        /// </summary>
        /// <remarks>
        /// There was a defect when the value was initialized in xaml and the control is 
        /// loaded into the heirarchy.
        /// </remarks>
        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_set_on_ancestor_in_xaml : With_ScopeProperty
        {
            [TestMethod, Asynchronous]
            public void Should_update_with_ancestor_xaml_value()
            {
                TestPanel.Children.Add(SystemUnderTestView);

                EnqueueConditional(
                    () => SystemUnderTestView.IsXamlContentLoaded);
                EnqueueCallback(() =>
                    AssertXamlObserverLog("Xaml"));
                EnqueueTestComplete();
            }
        }

        
        [TestClass, Tag("ScopeProperty")]
        public class When_unloaded_whilst_using_ancestor_property_value : With_ScopeProperty
        {
            public void Setup()
            {
                ScopePropertyFallback.Instance.SetValue(TestValueProperty, "Fallback");
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
                SystemUnderTestView.ObserverLog.Clear();

                TestPanel.Children.Add(SystemUnderTestView);
                EnqueueConditional(
                    () => SystemUnderTestView.IsObservedLoaded);
                EnqueueCallback(
                    () => SystemUnderTestView.Level2Ancestor.Children.Remove(SystemUnderTestView.Observed));
                EnqueueConditional(
                    () => !SystemUnderTestView.IsObservedLoaded);
            }

            [TestMethod, Asynchronous]
            public void Should_update_observer_to_fallback()
            {
                Setup();
                EnqueueCallback(
                   () => AssertObserverLog("Level1", "Fallback"));
                EnqueueTestComplete();
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_fallback_value_is_set : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                ScopePropertyFallback.Instance.SetValue(TestValueProperty, "Fallback");
            }

            [TestMethod]
            public void Should_update_observer_to_fallback()
            {
                AssertObserverLog("Fallback");
            }

            [TestMethod]
            public void Should_attach_behavior_to_fallback_instance()
            {
                Assert.IsNotNull(ScopePropertyBehavior.GetBehavior(ScopePropertyFallback.Instance, TestValueProperty));
            }

            [TestMethod]
            public void Should_subscribe_observer_to_behavior_on_fallback_instance()
            {
                Assert.IsTrue(ScopePropertyBehavior.GetBehavior(ScopePropertyFallback.Instance, TestValueProperty)
                    .HasObserver(SystemUnderTestView.Observer));
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_fallback_value_is_cleared : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                ScopePropertyFallback.Instance.SetValue(TestValueProperty, "Fallback");
                ScopePropertyFallback.Instance.ClearValue(TestValueProperty);
            }

            [TestMethod]
            public void Should_update_observer_to_unset_value()
            {
                AssertObserverLog("Fallback", TestUnsetValue);
            }
        }
        
        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_changed_on_ancestor_to_null_and_null_is_not_the_unset_value : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, null);
            }

            [TestMethod]
            public void Should_update_observed_to_null()
            {
                AssertObserverLog("Level1", null);
            }
        }

        [TestClass, Tag("ScopeProperty")]
        public class When_property_is_null_then_cleared_on_ancestor : With_ScopeProperty
        {
            [TestInitialize]
            public override void Given()
            {
                base.Given();
                SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, null);
                ScopePropertyFallback.Instance.SetValue(TestValueProperty, "Fallback");
                SystemUnderTestView.Level1Ancestor.ClearValue(TestValueProperty);
            }

            [TestMethod]
            public void Should_update_observed_to_fallback()
            {
                AssertObserverLog(null, "Fallback");
            }
        }

         [TestClass, Tag("ScopeProperty")]
        public class When_property_provider_is_changed : With_ScopeProperty
        {
             [TestInitialize]
             public override void Given()
             {
                 base.Given();
             }

             [TestMethod]
             public void Should_adjust_clients()
             {
                 // TODO: Move assertions into correct locations
                 ScopePropertyFallback.Instance.SetValue(TestValueProperty, "Fallback");

                 var fallbackScope = ScopePropertyBehavior.GetBehavior(ScopePropertyFallback.Instance, TestValueProperty);
                 Assert.IsNotNull(fallbackScope);
                 Assert.IsTrue(fallbackScope.HasObserver(SystemUnderTestView.Observer));

                 SystemUnderTestView.Level1Ancestor.SetValue(TestValueProperty, "Level1");
                 Assert.IsFalse(fallbackScope.HasObserver(SystemUnderTestView.Observer));

                 var scope = ScopePropertyBehavior.GetBehavior(SystemUnderTestView.Level1Ancestor, TestValueProperty);
                 Assert.IsNotNull(scope);
                 Assert.IsTrue(scope.HasObserver(SystemUnderTestView.Observer));
             }
        }
    }
}